spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
1import { ExternalLink } from 'lucide-react';2import type { Metadata } from 'next';3import Link from 'next/link';4import { notFound } from 'next/navigation';5import { SensorTierBadge, SignificanceBadge, StatusBadge } from '@/components/ui/badges';6import { KV, Row } from '@/components/ui/key-value';7import { LiveAgo } from '@/components/ui/live';8import { Container, Empty, Note, Stat, StatGrid } from '@/components/ui/section';9import { api, ApiError, safe } from '@/lib/api';10import { fmtDateTime, fmtDuration, fmtInt, fmtScore } from '@/lib/format';11import { routes, SENSOR_TIER_LABELS, SURFACE_LABELS } from '@/lib/site';1213export const revalidate = 60;14export const metadata: Metadata = { title: 'Sensor', robots: { index: false } };1516export default async function SensorPage({ params }: { params: Promise<{ id: string }> }) {17 const { id } = await params;18 let s;19 try {20 s = await api.sensor(id);21 } catch (e) {22 if (e instanceof ApiError && e.notFound) notFound();23 throw e;24 }25 const [snaps, changes] = await Promise.all([safe(api.sensorSnapshots(id, 50)), safe(api.sensorChanges(id, 50))]);26 const stale = s.last_success_at && Date.now() - new Date(s.last_success_at).getTime() > 2 * 86_400_000;27 return (28 <Container>29 <div className="pb-4 pt-6 md:pt-10">30 <p className="eyebrow flex flex-wrap items-center gap-2">31 Sensor <StatusBadge status={s.status} /> <SensorTierBadge tier={s.tier} withLabel />32 </p>33 <h1 className="display mt-2 text-[24px] md:text-[32px]">34 <Link href={routes.company(s.company.slug)} className="hover:text-accent">35 {s.company.display_name}36 </Link>{' '}37 <span className="text-ink-3">·</span> {SURFACE_LABELS[s.surface] ?? s.surface}38 </h1>39 <p className="mt-2 text-sm">40 <a href={s.url} target="_blank" rel="noopener noreferrer" className="link mono inline-flex items-center gap-1 break-all text-xs">41 {s.url} <ExternalLink className="size-3 shrink-0" aria-hidden />42 </a>43 </p>44 {stale && (45 <p className="mt-2 text-sm text-warning" role="status">46 Last successfully checked <LiveAgo at={s.last_success_at} tick={30000} />47 {s.last_failure_class ? ` — recent failures: ${s.last_failure_class} (${s.consecutive_failures} consecutive)` : ''}.48 </p>49 )}50 </div>51 <StatGrid cols={6}>52 <Stat label="Observations" value={fmtInt(s.observation_count)} size="sm" />53 <Stat label="Snapshots" value={fmtInt(s.snapshot_count)} size="sm" />54 <Stat label="Changes" value={fmtInt(s.change_count)} hint={`${fmtInt(s.meaningful_change_count)} meaningful`} size="sm" />55 <Stat label="Events" value={fmtInt(s.event_count)} size="sm" />56 <Stat label="Quality" value={fmtScore(s.quality_score)} size="sm" />57 <Stat label="Interval" value={fmtDuration(s.current_interval_s)} hint={`tier ${s.tier} · ${SENSOR_TIER_LABELS[s.tier] ?? ''}`} size="sm" />58 </StatGrid>59 <div className="mt-6 grid gap-8 lg:grid-cols-12">60 <div className="space-y-8 lg:col-span-8">61 <section>62 <p className="eyebrow mb-2">Snapshot versions</p>63 {!snaps ? (64 <Empty title="Snapshots unavailable." />65 ) : snaps.items.length === 0 ? (66 <Empty>No snapshot stored yet for this sensor.</Empty>67 ) : (68 <div className="table-scroll">69 <table className="data-table compact">70 <thead>71 <tr>72 <th className="num">v</th>73 <th>Fetched</th>74 <th>Title</th>75 <th className="num">Blocks</th>76 <th className="num">Text</th>77 <th>Hash</th>78 <th>Diff</th>79 </tr>80 </thead>81 <tbody>82 {snaps.items.map((v, i) => {83 const prev = snaps.items[i + 1];84 return (85 <tr key={v.id}>86 <td className="num tnum">{v.version_no}</td>87 <td className="tnum text-xs">88 <Link href={routes.snapshot(v.id)} className="link">89 {fmtDateTime(v.fetched_at)}90 </Link>91 </td>92 <td className="wrap text-ink-2">{v.title ?? '—'}</td>93 <td className="num tnum">{fmtInt(v.block_count)}</td>94 <td className="num tnum">{fmtInt(v.text_length)}</td>95 <td className="mono text-[11px] text-ink-3">{v.content_hash.replace('sha256:', '').slice(0, 10)}</td>96 <td className="text-xs">{prev ? <Link href={routes.snapshotDiff(prev.id, v.id)} className="link">vs v{prev.version_no}</Link> : <span className="text-ink-3">first</span>}</td>97 </tr>98 );99 })}100 </tbody>101 </table>102 </div>103 )}104 </section>105 <section>106 <p className="eyebrow mb-2">Detected changes</p>107 {!changes ? (108 <Empty title="Changes unavailable." />109 ) : changes.items.length === 0 ? (110 <Empty>No change detected yet.</Empty>111 ) : (112 <ul className="divide-y divide-rule border-y border-rule text-sm">113 {changes.items.map((c) => (114 <li key={c.id} className="flex flex-wrap items-center gap-2 py-2">115 <SignificanceBadge value={c.significance} />116 <Link href={routes.change(c.id)} className="link">117 {fmtDateTime(c.detected_at)}118 </Link>119 <span className="tnum text-xs text-ink-3">120 +{c.blocks_added} · −{c.blocks_removed} · ~{c.blocks_modified}121 </span>122 <span className="mono ml-auto text-[11px] text-ink-3">{c.kind}</span>123 </li>124 ))}125 </ul>126 )}127 </section>128 </div>129 <aside className="lg:col-span-4">130 <KV>131 <Row k="Connector">132 <span className="mono text-xs">{s.connector_id}</span>133 </Row>134 <Row k="Domain">135 <span className="mono text-xs">{s.domain}</span>136 </Row>137 <Row k="Discovery">138 {s.discovery_method ?? '—'} <span className="text-xs text-ink-3">· confidence {Math.round(s.discovery_confidence * 100)} %</span>139 </Row>140 <Row k="Last run">141 <LiveAgo at={s.last_run_at} tick={10000} />142 </Row>143 <Row k="Last success">144 <LiveAgo at={s.last_success_at} tick={10000} />145 </Row>146 <Row k="Last change">147 <LiveAgo at={s.last_change_at} tick={30000} />148 </Row>149 <Row k="Next run">{fmtDateTime(s.next_run_at)}</Row>150 <Row k="Last status">151 <span className="tnum">{s.last_status ?? '—'}</span>152 {s.last_failure_class && <span className="mono ml-2 text-xs text-danger">{s.last_failure_class}</span>}153 </Row>154 <Row k="Created">{fmtDateTime(s.created_at)}</Row>155 <Row k="Id">156 <span className="mono text-xs break-all">{s.id}</span>157 </Row>158 </KV>159 <Note className="mt-4">A sensor is a deployed connector instance attached to one public URL. It never bypasses authentication or challenges; when a page moves, the auto-repair loop tries redirects, sitemaps and navigation before flagging it for review.</Note>160 </aside>161 </div>162 </Container>163 );164}165